rm(list = ls())
library(data.table)
data.table 1.11.8 Latest news: r-datatable.com
Attaching package: ‘data.table’
The following object is masked _by_ ‘.GlobalEnv’:
.N
library(foreign)
library(kernlab)
library(MASS) # for mvrnorm
library(ggplot2)
Attaching package: ‘ggplot2’
The following object is masked from ‘package:kernlab’:
alpha
library(gridExtra)
library(glmnet)
Loading required package: Matrix
Loading required package: foreach
Loaded glmnet 2.0-16
library(caret)
package ‘caret’ was built under R version 3.5.2Loading required package: lattice
#library(plot3D)
library(memisc) #cases
Attaching package: ‘memisc’
The following object is masked from ‘package:foreach’:
foreach
The following object is masked from ‘package:Matrix’:
as.array
The following object is masked from ‘package:ggplot2’:
syms
The following objects are masked from ‘package:stats’:
contr.sum, contr.treatment, contrasts
The following object is masked from ‘package:base’:
as.array
setwd('~/github/bdr/')
source('functions.R')
Read in data
Data from 3 surveys:
All have a range of (overlapping) demographic variables AND ask about support in the 2018 midterms.
# import data
data_sept18 = data.table(read.spss('data/Sept18/Sept18 public.sav', to.data.frame = T), stringsAsFactors = F)
data_june18 = data.table(read.spss('data/June18/June18 public.sav', to.data.frame = T), stringsAsFactors = F)
data_may18 = data.table(read.spss('data/May18/May18 public.sav', to.data.frame = T), stringsAsFactors = F)
data_sept18
data_may18[, .N, party]
Specify where vars observed
The idea of this approach is to find a way to leverage the extensive/valuable data on the voterfile to outcomes observed in a survey (where more limited covariates are collected). Therefore, we need to assign variables to where we’re saying they’re observed (in the survey, in the voterfile or in both).
# specify where each var is observed (survey, file or both)
survey_vars = c('demo_mode', 'demo_education', 'demo_phonetype', 'month_called', 'demo_ideology', 'demo_party')
file_and_survey_vars = c('demo_sex', 'demo_age_bucket', 'demo_state', 'demo_income', 'demo_region', 'demo_race', 'demo_hispanic')
Recode Data
# RECODE all data sets
data_recoded = rbindlist(lapply(list(data_sept18, data_may18, data_june18), doPewRecode))
NAs introduced by coercionconditions are not mutually exclusivecondition is.na(age_num) is never satisfiedconditions are not mutually exclusiveconditions are not mutually exclusiveconditions are not mutually exclusiveconditions are not mutually exclusiveconditions are not mutually exclusiveconditions are not mutually exclusiveconditions are not mutually exclusiveconditions are not mutually exclusiveAdding new column 'qsupport' then assigning NULL (deleting it).NAs introduced by coercionconditions are not mutually exclusivecondition is.na(age_num) is never satisfiedconditions are not mutually exclusiveconditions are not mutually exclusiveconditions are not mutually exclusiveconditions are not mutually exclusiveconditions are not mutually exclusiveconditions are not mutually exclusiveconditions are not mutually exclusiveconditions are not mutually exclusiveAdding new column 'qsupport' then assigning NULL (deleting it).NAs introduced by coercionconditions are not mutually exclusivecondition is.na(age_num) is never satisfiedconditions are not mutually exclusiveconditions are not mutually exclusiveconditions are not mutually exclusiveconditions are not mutually exclusiveconditions are not mutually exclusiveconditions are not mutually exclusiveconditions are not mutually exclusiveconditions are not mutually exclusiveAdding new column 'qsupport' then assigning NULL (deleting it).
data_recoded
Categorize variables
Create a data table of all of the covars we have to work with
# create data table with vars and levels
covars = names(data_recoded)[grepl('demo', names(data_recoded))]
covars = data.table(do.call(rbind, lapply(covars, function(c){
cbind(c, data_recoded[, .(level = unique(get(c)))][order(level)])
})))
setnames(covars, c('var', 'level'))
covars[, level_modmat := paste0(var, level)]
covars[, in_survey := as.numeric(var %in% survey_vars)]
covars[, in_both := as.numeric(var %in% file_and_survey_vars)]
covars[, in_file := as.numeric(in_survey + in_both == 0)]
covars[, .N, .(in_survey, in_file, in_both)]
Get data partitions
We need to divide the data into the following partitions:
- Survey data - matched to the voterfile
- Survey data - unmatched to the voterfile
- Voterfile data
- Holdout set
We also want to mirror real conditions, in that the survey data is not selected completely at random from the voterfile - it should be biased. The same is true for the survey data that we can match to the voterfile. Therefore, we specify a “surveyed mechanism” and a “matched mechanism” based on my knowledge of common patterns observed in real situations.
Generally, those who respond to surveys tend to be older, unemployed/retired/disabled, partisan, more educated, have white collar jobs and reached on cell phones. We are more likely to be able to match someone to the voterfile if they are higher income (more consumer data available on them), reached on a cell-phone, older, white, live in a smaller household. These trends are reflected in the formula for \(p_\text{missing}\) and \(p_{matched}\) specified below.
Proability of surveyed
# scale age and set NAs to 0
data_recoded[, age_scaled := scale(age_num)]
data_recoded[is.na(age_scaled), age_scaled := 0]
data_recoded[, p_surveyed :=
(-2)
+ 2 * age_scaled
- 0.5 * is.na(age_num)
+ 1.5 * as.numeric(demo_mode == 'cell')
- 1.5 * as.numeric(demo_party == '05-Ind')
- 3 * as.numeric(demo_party == "99-DK/refused")
+ 1.5 * as.numeric(demo_education %in% c('01-postgrad', '02-bach'))
+ 3 * as.numeric(demo_ideology == 'Very conservative' | demo_ideology == 'Very liberal')
]
data_recoded[, p_surveyed := exp(p_surveyed)/(1 + exp(p_surveyed))]
hist(data_recoded[, p_surveyed])

data_recoded[, .(.N, mean(p_surveyed)), .(demo_age_bucket)][order(demo_age_bucket)]
data_recoded[, .(.N, mean(p_surveyed)), .(demo_mode)][order(demo_mode)]
data_recoded[, .(.N, mean(p_surveyed)), .(demo_party)][order(demo_party)]
data_recoded[, .(.N, mean(p_surveyed)), .(demo_ideology)][order(demo_ideology)]
Probability of matched
data_recoded[, p_matched := NULL]
Adding new column 'p_matched' then assigning NULL (deleting it).
data_recoded[, p_matched :=
-2 +
-2 * as.numeric(demo_mode == 'landline')
+ 3 * as.numeric(demo_race == 'W')
+ -2 * as.numeric(demo_reg == '03-No')
+ -1 * as.numeric(demo_hhsize == 2)
+ -2 * as.numeric(demo_hhsize == 3)
+ 2 *age_scaled
+ as.numeric(demo_income)/3
- 4* as.numeric(demo_income == '99-DK/refused')
]
data_recoded[, p_matched := exp(p_matched)/(1 + exp(p_matched))]
hist(data_recoded$p_matched)

data_recoded[, .(.N, mean(p_matched)), demo_mode]
data_recoded[, .(.N, mean(p_matched)), demo_hispanic]
data_recoded[, .(.N, mean(p_matched)), demo_age_bucket][order(demo_age_bucket)]
Check correlation between the generated probabilities
ggplot(data_recoded, aes(x = p_surveyed, y = p_matched)) + geom_point()

Test/training sets
Generate actual partitions based on \(p_\text{surveyed}\) and \(p_\text{matched}\). The holdout (test) set is selected first using \(p=1/n\) for all \(n\) units in the Pew data. From the remaining data, we first select the set of observed survey data proportional to \(p_\text{surveyed}\). Last, from the surveyed data, we select the subset of data that matches to the voterfile with probability \(p_\text{matched}\).
testtrain = getTestTrain(data = data_recoded
, n_holdout = 1000, n_surveyed = 2000, n_matched = 1000
, p_surveyed = data_recoded$p_surveyed
, p_matched = data_recoded$p_matched
)
Adding new column 'holdout' then assigning NULL (deleting it).Adding new column 'surveyed' then assigning NULL (deleting it).Adding new column 'matched' then assigning NULL (deleting it).
data_recoded = testtrain$data
# N and pct voting dem by partition
data_recoded[, .(.N, mean(y_dem)), list(holdout, surveyed, matched, voterfile)]
# check prop surveyed and matched by demos
data_recoded[, .(.N, prop_surveyed = mean(surveyed), prop_matched = sum(matched)/sum(surveyed), overall_matched = mean(matched)), demo_age_bucket][order(demo_age_bucket)]
data_recoded[, .(.N, prop_surveyed = mean(surveyed), prop_matched = sum(matched)/sum(surveyed), overall_matched = mean(matched)), demo_party][order(demo_party)]
Do basic dist regression, one step at a time
First, we’ll walk through each step in distribution regression, fixing hyperparameters:
- Bag the data. Since we actually observed our outcome at the individual level (not at the bag level), we have to bag the data ourselves. We’ll do this with k-means using the variables observed both in the survey AND in the voterfile. This is necessary so that we can link the bagged outcomes to the voterfile data.
- Get landmark points. In order to cut down on the dimensionality of the task, we’ll identify a set of \(L\) landmark points \(\mathbf{u} = (u_1, \dots, u_L)\), also using k-means. The landmarks will be the centroids of the clusters indentified with k-means. We’ll use the landmark points to embed the voterfile data in feature space.
- Embed in feature space. We need to embed the voterfile data in feature space. We do this with a Gaussian kernel with length-scale \(\sigma\). The explicit feature representation for \(x_i\) is given by \(\phi(x_i) = [k(x_i, u_1), \dots, k(x_i, u_L)]\)
- Calculate the empirical mean of the embedded features. Calculate the empirical mean of the explicit feature representations of each bag \(j = 1, \dots, J\), \(\hat{\mu}_j = \sum_{i=1}^{N_j} \phi(x_i)\)
- Do regularized regression. Regress the bagged outcome \(y_j\) on the empirical mean embedding of each bag \(\mu_j\). We use a LASSO here, though we could use elastic net and optimize \(\alpha\) as well as \(\lambda\).
- Predict. Use predict \(\hat{y}_i\) (at the individual-level) using the regression coefficients learned in step 5.
These steps are implemented below.
Some questions:
- Should we tune n_bags and n_landmarks like we do \(\sigma\)?
- Should we generate bags based on the whole dataset (survey + voterfile), or just based on the survey data? Right now I’m generating the bags using only the survey data. In practice, the voterfile data will have MANY more observations than the survey data, so it’s very unlikely that the survey will have bags that are empty in thhe file, while if we used only the voterfile data or voterfile + survey, much more likely that bags will be missing from the survey data.
- Should the landmarks be a holdout set or centroids from k-means? Andvantages/disadvantages? I imagine an advantage of the centroids is that you don’t lose N for the mean embedding?
- Should I have a 5th data partition for tuning the hyperparams (right now I’m using the test set)?
- Should I switch from LASSO to cv-chosen \(\alpha\)?
Assign bags
#### Create bags of survey responses using variables observed in BOTH survey AND voterfile
# should we make the bags with the subset of data from the survey, or the whole file???
# sigma = 0.003 ## chosen with median heuristic
n_bags = 50
n_landmarks = 12
bags = getBags(data = data_recoded[surveyed == 1,]
, vars = file_and_survey_vars
, n_bags = n_bags
, newdata = data_recoded[voterfile == 1 | holdout == 1, ])
table(bags$bags)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
34 13 62 75 41 30 59 47 35 55 72 72 22 61 85 20 20 39 24 17 45 52 30 24 16 37 13
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
48 15 18 19 58 23 30 23 46 44 32 56 43 41 107 59 32 43 36 16 55 18 38
length(unique(bags$bags))
[1] 50
data_recoded[surveyed == 1, bag := bags$bags]
data_recoded[voterfile == 1 | holdout == 1, bag := bags$bags_newdata]
data_recoded[, .(.N, mean(surveyed)), .(bag)][order(bag)]
Get landmark points
landmarks = getLandmarks(data = data_recoded
, vars = unique(covars[in_both == 1 | in_file == 1,]$var)
, n_landmarks = n_landmarks
, subset_ind = (data_recoded$voterfile == 1))
X_file = landmarks$X[data_recoded$voterfile == 1, ]
Embed file in feature space
Choose scale parameter \(\sigma\) for RBF kernel - use median heuristic for now
rbf1 = rbfdot(sigma = 1)
K_sigma = kernelMatrix(rbf1, x = as.matrix(X_file), y = landmarks$landmarks)
sigma = median(K_sigma)
train_bag = data_recoded[voterfile == 1, bag]
test_bag = data_recoded[holdout == 1, bag]
# get features
features = getFeatures(data = landmarks$X
, bag = data_recoded$bag
, train_ind = data_recoded$voterfile # the col of indicators for the training set
, landmarks = landmarks$landmarks
, sigma = sigma)
Fit LASSO using mean feature embeddings
calcMSE(Y = data_recoded$y_dem, Y_pred = data_recoded$y_dem_basicdr)
[1] 0.2970134
Plot results
data_recoded[voterfile == 1 | holdout == 1, y_dem_basicdr_dec := cut(y_dem_basicdr, breaks = quantile(y_dem_basicdr, probs = seq(0,1,0.1)), labels = 1:10, include.lowest = T)]
ggplot(data_recoded[voterfile == 1, .(pct_y_dem = mean(y_dem)), by = y_dem_basicdr_dec], aes(x = y_dem_basicdr_dec, y = pct_y_dem)) +
geom_bar(stat = 'identity') +
ggtitle("Pct Dem supporter by score decile - voterfile")

ggplot(data_recoded[holdout == 1, .(pct_y_dem = mean(y_dem)), by = y_dem_basicdr_dec], aes(x = y_dem_basicdr_dec, y = pct_y_dem)) +
geom_bar(stat = 'identity') +
ggtitle("Pct Dem supporter by score decile - holdout")

Simple distribution regression with tuned hyperparameters
Tune hyperparameters
Use hyperband algorithm described here - https://medium.com/criteo-labs/hyper-parameter-optimization-algorithms-2fe447525903
Main advantage of this method are that it runs multiple iterations with the same parameters (there’s still an element of randomness in how the initital centroids are chosen in each k-means run, so this helps with evaluating mean performance of each set of hyperparams).
Disadvantage is that we only consider 64 randomly-selected sets of hyperparameters, which may not fully explore the space enough. Though the hyperparameters chosen do seem to vastly improve performance over initial (non-tuned) choices.
library(parallel)
library(MASS)
# takes a while, so don't run by accident/automatically
if(FALSE){
# https://medium.com/criteo-labs/hyper-parameter-optimization-algorithms-2fe447525903
n_param_sets = 64
results = data.table(sigma = exp(runif(min = -15, max = -1, n = n_param_sets))
, n_landmarks = round(runif(min = 10, max = 400, n = n_param_sets))
, n_bags = round(exp(runif(min = 2, max = 5, n = n_param_sets)))
, round = rep(0, n_param_sets)
, mse = rep(0, n_param_sets)
, mse_count = rep(0, n_param_sets)
)
#setnames(results, c('sigma','n_landmarks', 'n_bags', 'mse'))
#initialize counters
which_left = 1:n_param_sets
n_iter = 10
while(length(which_left) > 1){
cat(paste(Sys.time(), "round: ", max(results[, round] + 1)), ', remaining: ',length(which_left),'\n')
# double the number of iterations
#if(this_round > 3) n_iter = 50
for(p in which_left){
#cat(paste('\t param:', p, '\n'))
# increment round
results[p, round := round + 1]
mse_sum = unlist(mclapply(1:n_iter, function(i){
tryCatch(doBasicDR(data = data_recoded
, bagging_vars = file_and_survey_vars
, regression_vars = unique(covars[in_both == 1 | in_file == 1,]$var)
#, outcome = 'y_dem'
, outcome = c('y_dem', 'y_rep', 'y_oth')
, family = 'multinomial'
, n_bags = results[p, n_bags]
, n_landmarks = results[p, n_landmarks]
, sigma = results[p, sigma]
, bagging_ind = 'surveyed'
, train_ind = 'voterfile'
, test_ind = 'holdout'
)$mse_test
, error = function(e) {
print(e)
return(0)})
}, mc.cores = 5 #need this for parallelization
))
results[p, mse := mse + sum(mse_sum)]
results[p, mse_count := mse_count + sum(mse_sum > 0)]
}
which_left = which(results[which_left, mse/mse_count < median(mse/mse_count, na.rm = T)])
}
}
Fit simple distribution regression with optimal hyperparams
# params from per_hyperparam_opt.R
fit_basicDR = doBasicDR(data = data_recoded
, bagging_vars = file_and_survey_vars
, regression_vars = regression_vars
, outcome = 'y_dem'
, n_bags = 95
, n_landmarks = 45
, sigma = 0.004
, bagging_ind = 'surveyed'
, train_ind = 'voterfile'
, test_ind = 'holdout')
2019-07-31 16:59:42 Making bags
2019-07-31 16:59:42 Getting landmarks
2019-07-31 16:59:42 Making features
2019-07-31 16:59:43 Fitting model
Plot results
# get score deciles
fit_basicDR$data[voterfile == 1 | holdout == 1, y_hat_dec := cut(y_dem_hat, breaks = quantile(y_dem_hat, probs = seq(0,1,0.1)), labels = 1:10, include.lowest = T)]
# plot voterfile data
ggplot(fit_basicDR$data[voterfile == 1, .(pct_y_hat = mean(y_dem_hat)), by = y_hat_dec], aes(x = y_hat_dec, y = pct_y_hat)) +
geom_bar(stat = 'identity') +
ggtitle("Pct Dem supporter by score decile - voterfile")

# plot holdout data
ggplot(fit_basicDR$data[holdout == 1, .(pct_y_hat = mean(y_dem_hat)), by = y_hat_dec], aes(x = y_hat_dec, y = pct_y_hat)) +
geom_bar(stat = 'identity') +
ggtitle("Pct Dem supporter by score decile - holdout")

Compare to basic LASSO and group average
As a baseline, we’ll fit a simple LASSO using only the \(n_matched\) observations that were matched to the file. We fit the LASSO once to find which coefs are non-zero, and then re-fit with only those covars and no penalty to avoid shrinkage.
The other baseline we’ll use is the mean in each bag observed in the survey data (matched and unmatched). This is our dependent variable for the distribution regression.
Fit, predict, and plot results
X_matched = landmarks$X[data_recoded$matched == 1, ]
lasso_fit = cv.glmnet(x = X_matched
, y = data_recoded[matched == 1, ]$y_dem
, nfolds = 10
, family = 'binomial')
nonzero_ind = which(coef(lasso_fit, s = 'lambda.min')[-1] != 0)
# re-fit to avoid shrinkage
lasso_fit = glmnet(x = X_matched[, nonzero_ind]
, y = data_recoded[matched == 1, ]$y_dem
, lambda = 0
, family = 'binomial')
# predict on full dataset
data_recoded[, y_hat_lasso := predict(lasso_fit, newx = landmarks$X[, nonzero_ind], type = 'response')]
# get score deciles
data_recoded[holdout == 1, y_hat_lasso_dec := cut(y_hat_lasso, breaks = quantile(y_hat_lasso, probs = seq(0,1,0.1)), labels = 1:10, include.lowest = T)]
# plot avg outcome by score decile
ggplot(data_recoded[holdout == 1, .(pct_y_hat = mean(y_hat_lasso)), by = y_hat_lasso_dec], aes(x = y_hat_lasso_dec, y = pct_y_hat)) +
geom_bar(stat = 'identity') +
ggtitle("Pct Dem supporter by score decile - holdout")

Merge bag average with full file
data_recoded[Y_svy_bag, on = 'bag', y_bagmean := i.y_mean]
Calculate the MSE of each the basic DR and the LASSO
data.frame(method = c("Dist Reg", "Logit", "Bag mean")
, MSE = c(calcMSE(Y = data_recoded[holdout == 1, y_dem], data_recoded[holdout == 1, y_dem_hat])
, calcMSE(Y = data_recoded[holdout == 1, y_dem], data_recoded[holdout == 1, y_hat_lasso])
, calcMSE(Y = data_recoded[holdout == 1, y_dem], data_recoded[holdout == 1, y_bagmean])))
See how well each method is predicting the overall topline % dem support (bias)
data_recoded[, .(actual = mean(y_dem)
, basicdr = mean(y_dem_hat)
, logit = mean(y_hat_lasso)
, groupavg = mean(y_bagmean, na.rm =T))
, by = .(partition = ifelse(holdout == 1, 'holdout', ifelse(voterfile == 1, 'voterfile', ifelse(matched == 1, 'survey-matched', 'survey-unmatched'))))]
Some observations
- The choice of where variables are observed (survey, file or both) has a large impact on results. For example, if we observe someone’s stated party affiliation at the individual-level in the voterfile, the logit vastly outperforms the distribution regression (ex. MSE 0.08 v. 0.25). However, if we only observe that variable in the survey, the distribution regresion does almost as well (ex. MSE 0.22 v. 0.25).
Multinomial Outcome
Now we’ll switch to a multinomial outcome \(\mathbf{y} = (y_\text{dem}, y_\text{rep}, y_\text{other})\)
fit_basicDR$mse_test
[1] 0.1848039
Calculate group means
Fit multinomial LASSO
X_matched = landmarks$X[data_recoded$matched == 1, ]
lasso_fit = cv.glmnet(x = X_matched
, y = as.matrix(data_recoded[matched == 1, which(names(data_recoded) %in% outcome), with = F])
, nfolds = 10
, family = 'multinomial'
)
nonzero_ind = sort(unique(unlist(lapply(coef(lasso_fit, s = 'lambda.min'), function(c){
which(c[-1] != 0)
}))))
if(length(nonzero_ind) == 0){
nonzero_ind = 1:ncol(X_matched)
}
# # re-fit to avoid shrinkage
lasso_fit = glmnet(x = X_matched[, nonzero_ind]
, y = as.matrix(data_recoded[matched == 1, which(names(data_recoded) %in% outcome), with = F])
, lambda = 0
, family = 'multinomial')
# predict on full dataset
data_recoded[, c('y_logit_dem', 'y_logit_rep', 'y_logit_oth') := as.list(data.table(matrix(logit_multinom, ncol= 3)))]
Compare the MSEs of the holdout set
# compare MSE
data.frame(
method = c('Dist Reg', 'Logit','Bag avg')
, MSE = c(calcMSE(Y = data_recoded[holdout == 1, get(outcome)], data_recoded[holdout == 1, get(paste0(outcome, '_hat'))])
, calcMSE(Y = data_recoded[holdout == 1, get(outcome)], data_recoded[holdout == 1, get(c('y_logit_dem', 'y_logit_rep', 'y_logit_oth'))])
, calcMSE(Y = data_recoded[holdout == 1, get(outcome)], data_recoded[holdout == 1, get(c('y_dem_grpmean', 'y_rep_grpmean', 'y_oth_grpmean'))]))
)
LS0tCnRpdGxlOiAiQkRSIHdpdGggUGV3IERhdGEiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCgpgYGB7ciBzZXQtdXB9CnJtKGxpc3QgPSBscygpKQpsaWJyYXJ5KGRhdGEudGFibGUpCmxpYnJhcnkoZm9yZWlnbikKbGlicmFyeShrZXJubGFiKQpsaWJyYXJ5KE1BU1MpICAjIGZvciBtdnJub3JtCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShncmlkRXh0cmEpCmxpYnJhcnkoZ2xtbmV0KQpsaWJyYXJ5KGNhcmV0KQojbGlicmFyeShwbG90M0QpCmxpYnJhcnkobWVtaXNjKSAjY2FzZXMKc2V0d2QoJ34vZ2l0aHViL2Jkci8nKQoKc291cmNlKCdmdW5jdGlvbnMuUicpCmBgYAoKCgojIyBSZWFkIGluIGRhdGEKRGF0YSBmcm9tIDMgc3VydmV5czoKCiogaHR0cHM6Ly93d3cucGVvcGxlLXByZXNzLm9yZy9kYXRhc2V0L3NlcHRlbWJlci0yMDE4LXBvbGl0aWNhbC1zdXJ2ZXkvCiogaHR0cHM6Ly93d3cucGVvcGxlLXByZXNzLm9yZy9kYXRhc2V0L2p1bmUtMjAxOC1wb2xpdGljYWwtc3VydmV5LwoqIGh0dHBzOi8vd3d3LnBlb3BsZS1wcmVzcy5vcmcvZGF0YXNldC9tYXktMjAxOC1wb2xpdGljYWwtc3VydmV5LwoKQWxsIGhhdmUgYSByYW5nZSBvZiAob3ZlcmxhcHBpbmcpIGRlbW9ncmFwaGljIHZhcmlhYmxlcyBBTkQgYXNrIGFib3V0IHN1cHBvcnQgaW4gdGhlIDIwMTggbWlkdGVybXMuCgpgYGB7ciBkYXRhLCB3YXJuaW5nPUZBTFNFfQoKIyBpbXBvcnQgZGF0YQpkYXRhX3NlcHQxOCA9IGRhdGEudGFibGUocmVhZC5zcHNzKCdkYXRhL1NlcHQxOC9TZXB0MTggcHVibGljLnNhdicsIHRvLmRhdGEuZnJhbWUgPSBUKSwgc3RyaW5nc0FzRmFjdG9ycyA9IEYpCgpkYXRhX2p1bmUxOCA9IGRhdGEudGFibGUocmVhZC5zcHNzKCdkYXRhL0p1bmUxOC9KdW5lMTggcHVibGljLnNhdicsIHRvLmRhdGEuZnJhbWUgPSBUKSwgc3RyaW5nc0FzRmFjdG9ycyA9IEYpCgpkYXRhX21heTE4ID0gZGF0YS50YWJsZShyZWFkLnNwc3MoJ2RhdGEvTWF5MTgvTWF5MTggcHVibGljLnNhdicsIHRvLmRhdGEuZnJhbWUgPSBUKSwgc3RyaW5nc0FzRmFjdG9ycyA9IEYpCgpkYXRhX3NlcHQxOAoKZGF0YV9tYXkxOFssIC5OLCBwYXJ0eV0KYGBgCgojIyBTcGVjaWZ5IHdoZXJlIHZhcnMgb2JzZXJ2ZWQKClRoZSBpZGVhIG9mIHRoaXMgYXBwcm9hY2ggaXMgdG8gZmluZCBhIHdheSB0byBsZXZlcmFnZSB0aGUgZXh0ZW5zaXZlL3ZhbHVhYmxlIGRhdGEgb24gdGhlIHZvdGVyZmlsZSB0byBvdXRjb21lcyBvYnNlcnZlZCBpbiBhIHN1cnZleSAod2hlcmUgbW9yZSBsaW1pdGVkIGNvdmFyaWF0ZXMgYXJlIGNvbGxlY3RlZCkuICBUaGVyZWZvcmUsIHdlIG5lZWQgdG8gYXNzaWduIHZhcmlhYmxlcyB0byB3aGVyZSB3ZSdyZSBzYXlpbmcgdGhleSdyZSBvYnNlcnZlZCAoaW4gdGhlIHN1cnZleSwgaW4gdGhlIHZvdGVyZmlsZSBvciBpbiBib3RoKS4KYGBge3J9CiMgc3BlY2lmeSB3aGVyZSBlYWNoIHZhciBpcyBvYnNlcnZlZCAoc3VydmV5LCBmaWxlIG9yIGJvdGgpCnN1cnZleV92YXJzID0gYygnZGVtb19tb2RlJywgJ2RlbW9fZWR1Y2F0aW9uJywgJ2RlbW9fcGhvbmV0eXBlJywgJ21vbnRoX2NhbGxlZCcsICdkZW1vX2lkZW9sb2d5JywgJ2RlbW9fcGFydHknKQpmaWxlX2FuZF9zdXJ2ZXlfdmFycyA9IGMoJ2RlbW9fc2V4JywgJ2RlbW9fYWdlX2J1Y2tldCcsICdkZW1vX3N0YXRlJywgJ2RlbW9faW5jb21lJywgJ2RlbW9fcmVnaW9uJywgJ2RlbW9fcmFjZScsICdkZW1vX2hpc3BhbmljJykKYGBgCgoKIyMgUmVjb2RlIERhdGEKYGBge3IsIG1lc3NhZ2UgPSBGLCB3YXJuaW5nPUZ9CiMgUkVDT0RFIGFsbCBkYXRhIHNldHMKZGF0YV9yZWNvZGVkID0gcmJpbmRsaXN0KGxhcHBseShsaXN0KGRhdGFfc2VwdDE4LCBkYXRhX21heTE4LCBkYXRhX2p1bmUxOCksIGRvUGV3UmVjb2RlKSkKCmRhdGFfcmVjb2RlZApgYGAKCgojIyBDYXRlZ29yaXplIHZhcmlhYmxlcwpDcmVhdGUgYSBkYXRhIHRhYmxlIG9mIGFsbCBvZiB0aGUgY292YXJzIHdlIGhhdmUgdG8gd29yayB3aXRoCgpgYGB7cn0KIyBjcmVhdGUgZGF0YSB0YWJsZSB3aXRoIHZhcnMgYW5kIGxldmVscwpjb3ZhcnMgPSBuYW1lcyhkYXRhX3JlY29kZWQpW2dyZXBsKCdkZW1vJywgbmFtZXMoZGF0YV9yZWNvZGVkKSldCmNvdmFycyA9IGRhdGEudGFibGUoZG8uY2FsbChyYmluZCwgbGFwcGx5KGNvdmFycywgZnVuY3Rpb24oYyl7CiAgY2JpbmQoYywgZGF0YV9yZWNvZGVkWywgLihsZXZlbCA9IHVuaXF1ZShnZXQoYykpKV1bb3JkZXIobGV2ZWwpXSkKfSkpKQpzZXRuYW1lcyhjb3ZhcnMsIGMoJ3ZhcicsICdsZXZlbCcpKQpjb3ZhcnNbLCBsZXZlbF9tb2RtYXQgOj0gcGFzdGUwKHZhciwgbGV2ZWwpXQoKY292YXJzWywgaW5fc3VydmV5IDo9IGFzLm51bWVyaWModmFyICVpbiUgc3VydmV5X3ZhcnMpXQpjb3ZhcnNbLCBpbl9ib3RoIDo9IGFzLm51bWVyaWModmFyICVpbiUgZmlsZV9hbmRfc3VydmV5X3ZhcnMpXQpjb3ZhcnNbLCBpbl9maWxlIDo9IGFzLm51bWVyaWMoaW5fc3VydmV5ICsgaW5fYm90aCA9PSAwKV0KCmNvdmFyc1ssIC5OLCAuKGluX3N1cnZleSwgaW5fZmlsZSwgaW5fYm90aCldCmBgYAoKIyMgR2V0IGRhdGEgcGFydGl0aW9ucwoKV2UgbmVlZCB0byBkaXZpZGUgdGhlIGRhdGEgaW50byB0aGUgZm9sbG93aW5nIHBhcnRpdGlvbnM6CgoqIFN1cnZleSBkYXRhIC0gKm1hdGNoZWQqIHRvIHRoZSB2b3RlcmZpbGUKKiBTdXJ2ZXkgZGF0YSAtICp1bm1hdGNoZWQqIHRvIHRoZSB2b3RlcmZpbGUKKiBWb3RlcmZpbGUgZGF0YQoqIEhvbGRvdXQgc2V0CgpXZSBhbHNvIHdhbnQgdG8gbWlycm9yIHJlYWwgY29uZGl0aW9ucywgaW4gdGhhdCB0aGUgc3VydmV5IGRhdGEgaXMgbm90IHNlbGVjdGVkIGNvbXBsZXRlbHkgYXQgcmFuZG9tIGZyb20gdGhlIHZvdGVyZmlsZSAtIGl0IHNob3VsZCBiZSBiaWFzZWQuICBUaGUgc2FtZSBpcyB0cnVlIGZvciB0aGUgc3VydmV5IGRhdGEgdGhhdCB3ZSBjYW4gbWF0Y2ggdG8gdGhlIHZvdGVyZmlsZS4gIFRoZXJlZm9yZSwgd2Ugc3BlY2lmeSBhICJzdXJ2ZXllZCBtZWNoYW5pc20iIGFuZCBhICJtYXRjaGVkIG1lY2hhbmlzbSIgYmFzZWQgb24gbXkga25vd2xlZGdlIG9mIGNvbW1vbiBwYXR0ZXJucyBvYnNlcnZlZCBpbiByZWFsIHNpdHVhdGlvbnMuCgpHZW5lcmFsbHksIHRob3NlIHdobyByZXNwb25kIHRvIHN1cnZleXMgdGVuZCB0byBiZSBvbGRlciwgdW5lbXBsb3llZC9yZXRpcmVkL2Rpc2FibGVkLCBwYXJ0aXNhbiwgbW9yZSBlZHVjYXRlZCwgaGF2ZSB3aGl0ZSBjb2xsYXIgam9icyBhbmQgcmVhY2hlZCBvbiBjZWxsIHBob25lcy4gIFdlIGFyZSBtb3JlIGxpa2VseSB0byBiZSBhYmxlIHRvIG1hdGNoIHNvbWVvbmUgdG8gdGhlIHZvdGVyZmlsZSBpZiB0aGV5IGFyZSBoaWdoZXIgaW5jb21lIChtb3JlIGNvbnN1bWVyIGRhdGEgYXZhaWxhYmxlIG9uIHRoZW0pLCByZWFjaGVkIG9uIGEgY2VsbC1waG9uZSwgb2xkZXIsIHdoaXRlLCBsaXZlIGluIGEgc21hbGxlciBob3VzZWhvbGQuICBUaGVzZSB0cmVuZHMgYXJlIHJlZmxlY3RlZCBpbiB0aGUgZm9ybXVsYSBmb3IgJHBfXHRleHR7bWlzc2luZ30kIGFuZCAkcF97bWF0Y2hlZH0kIHNwZWNpZmllZCBiZWxvdy4KCiMjIyBQcm9hYmlsaXR5IG9mIHN1cnZleWVkCmBgYHtyfQojIHNjYWxlIGFnZSBhbmQgc2V0IE5BcyB0byAwCmRhdGFfcmVjb2RlZFssIGFnZV9zY2FsZWQgOj0gc2NhbGUoYWdlX251bSldCmRhdGFfcmVjb2RlZFtpcy5uYShhZ2Vfc2NhbGVkKSwgYWdlX3NjYWxlZCA6PSAwXQoKZGF0YV9yZWNvZGVkWywgcF9zdXJ2ZXllZCA6PSAKICAgICAgICAgICAgICAgKC0yKQogICAgICAgICAgICAgKyAyICogYWdlX3NjYWxlZCAKICAgICAgICAgICAgIC0gMC41ICogaXMubmEoYWdlX251bSkgCiAgICAgICAgICAgICArIDEuNSAqIGFzLm51bWVyaWMoZGVtb19tb2RlID09ICdjZWxsJykgCiAgICAgICAgICAgICAtIDEuNSAqIGFzLm51bWVyaWMoZGVtb19wYXJ0eSA9PSAnMDUtSW5kJykKICAgICAgICAgICAgIC0gMyAqIGFzLm51bWVyaWMoZGVtb19wYXJ0eSA9PSAiOTktREsvcmVmdXNlZCIpIAogICAgICAgICAgICAgKyAxLjUgKiBhcy5udW1lcmljKGRlbW9fZWR1Y2F0aW9uICVpbiUgYygnMDEtcG9zdGdyYWQnLCAnMDItYmFjaCcpKQogICAgICAgICAgICAgKyAzICogYXMubnVtZXJpYyhkZW1vX2lkZW9sb2d5ID09ICdWZXJ5IGNvbnNlcnZhdGl2ZScgfCBkZW1vX2lkZW9sb2d5ID09ICdWZXJ5IGxpYmVyYWwnKQogICAgICAgICAgICAgXQpkYXRhX3JlY29kZWRbLCBwX3N1cnZleWVkIDo9IGV4cChwX3N1cnZleWVkKS8oMSArIGV4cChwX3N1cnZleWVkKSldCmhpc3QoZGF0YV9yZWNvZGVkWywgcF9zdXJ2ZXllZF0pCgpkYXRhX3JlY29kZWRbLCAuKC5OLCBtZWFuKHBfc3VydmV5ZWQpKSwgLihkZW1vX2FnZV9idWNrZXQpXVtvcmRlcihkZW1vX2FnZV9idWNrZXQpXQpkYXRhX3JlY29kZWRbLCAuKC5OLCBtZWFuKHBfc3VydmV5ZWQpKSwgLihkZW1vX21vZGUpXVtvcmRlcihkZW1vX21vZGUpXQpkYXRhX3JlY29kZWRbLCAuKC5OLCBtZWFuKHBfc3VydmV5ZWQpKSwgLihkZW1vX3BhcnR5KV1bb3JkZXIoZGVtb19wYXJ0eSldCmRhdGFfcmVjb2RlZFssIC4oLk4sIG1lYW4ocF9zdXJ2ZXllZCkpLCAuKGRlbW9faWRlb2xvZ3kpXVtvcmRlcihkZW1vX2lkZW9sb2d5KV0KYGBgCgojIyMgUHJvYmFiaWxpdHkgb2YgbWF0Y2hlZApgYGB7cn0KZGF0YV9yZWNvZGVkWywgcF9tYXRjaGVkIDo9IE5VTExdCmRhdGFfcmVjb2RlZFssIHBfbWF0Y2hlZCA6PQogICAgICAgICAgICAgICAtMiArCiAgICAgICAgICAgICAgIC0yICogYXMubnVtZXJpYyhkZW1vX21vZGUgPT0gJ2xhbmRsaW5lJykgCiAgICAgICAgICAgICArIDMgKiBhcy5udW1lcmljKGRlbW9fcmFjZSA9PSAnVycpCiAgICAgICAgICAgICArIC0yICogYXMubnVtZXJpYyhkZW1vX3JlZyA9PSAnMDMtTm8nKQogICAgICAgICAgICAgKyAtMSAqIGFzLm51bWVyaWMoZGVtb19oaHNpemUgPT0gMikKICAgICAgICAgICAgICsgLTIgKiBhcy5udW1lcmljKGRlbW9faGhzaXplID09IDMpCiAgICAgICAgICAgICArIDIgKmFnZV9zY2FsZWQKICAgICAgICAgICAgICsgYXMubnVtZXJpYyhkZW1vX2luY29tZSkvMwogICAgICAgICAgICAgLSA0KiBhcy5udW1lcmljKGRlbW9faW5jb21lID09ICc5OS1ESy9yZWZ1c2VkJykKICAgICAgICAgICAgIF0KZGF0YV9yZWNvZGVkWywgcF9tYXRjaGVkIDo9IGV4cChwX21hdGNoZWQpLygxICsgZXhwKHBfbWF0Y2hlZCkpXQpoaXN0KGRhdGFfcmVjb2RlZCRwX21hdGNoZWQpCgpkYXRhX3JlY29kZWRbLCAuKC5OLCBtZWFuKHBfbWF0Y2hlZCkpLCBkZW1vX21vZGVdCmRhdGFfcmVjb2RlZFssIC4oLk4sIG1lYW4ocF9tYXRjaGVkKSksIGRlbW9faGlzcGFuaWNdCmRhdGFfcmVjb2RlZFssIC4oLk4sIG1lYW4ocF9tYXRjaGVkKSksIGRlbW9fYWdlX2J1Y2tldF1bb3JkZXIoZGVtb19hZ2VfYnVja2V0KV0KYGBgCgpDaGVjayBjb3JyZWxhdGlvbiBiZXR3ZWVuIHRoZSBnZW5lcmF0ZWQgcHJvYmFiaWxpdGllcwpgYGB7cn0KZ2dwbG90KGRhdGFfcmVjb2RlZCwgYWVzKHggPSBwX3N1cnZleWVkLCB5ID0gcF9tYXRjaGVkKSkgKyBnZW9tX3BvaW50KCkKYGBgCgoKCiMjIyBUZXN0L3RyYWluaW5nIHNldHMKR2VuZXJhdGUgYWN0dWFsIHBhcnRpdGlvbnMgYmFzZWQgb24gJHBfXHRleHR7c3VydmV5ZWR9JCBhbmQgJHBfXHRleHR7bWF0Y2hlZH0kLiAgVGhlIGhvbGRvdXQgKHRlc3QpIHNldCBpcyBzZWxlY3RlZCBmaXJzdCB1c2luZyAkcD0xL24kIGZvciBhbGwgJG4kIHVuaXRzIGluIHRoZSBQZXcgZGF0YS4gIEZyb20gdGhlIHJlbWFpbmluZyBkYXRhLCB3ZSBmaXJzdCBzZWxlY3QgdGhlIHNldCBvZiBvYnNlcnZlZCBzdXJ2ZXkgZGF0YSBwcm9wb3J0aW9uYWwgdG8gJHBfXHRleHR7c3VydmV5ZWR9JC4gIExhc3QsIGZyb20gdGhlIHN1cnZleWVkIGRhdGEsIHdlIHNlbGVjdCB0aGUgc3Vic2V0IG9mIGRhdGEgdGhhdCBtYXRjaGVzIHRvIHRoZSB2b3RlcmZpbGUgd2l0aCBwcm9iYWJpbGl0eSAkcF9cdGV4dHttYXRjaGVkfSQuCgpgYGB7cn0KdGVzdHRyYWluID0gZ2V0VGVzdFRyYWluKGRhdGEgPSBkYXRhX3JlY29kZWQKICAgICAgICAgICAgICwgbl9ob2xkb3V0ID0gMTAwMCwgbl9zdXJ2ZXllZCA9IDIwMDAsIG5fbWF0Y2hlZCA9IDEwMDAKICAgICAgICAgICAgICwgcF9zdXJ2ZXllZCA9IGRhdGFfcmVjb2RlZCRwX3N1cnZleWVkCiAgICAgICAgICAgICAsIHBfbWF0Y2hlZCA9IGRhdGFfcmVjb2RlZCRwX21hdGNoZWQKICAgICAgICAgICAgICkKZGF0YV9yZWNvZGVkID0gdGVzdHRyYWluJGRhdGEKCiMgTiBhbmQgcGN0IHZvdGluZyBkZW0gYnkgcGFydGl0aW9uCmRhdGFfcmVjb2RlZFssIC4oLk4sIG1lYW4oeV9kZW0pKSwgbGlzdChob2xkb3V0LCBzdXJ2ZXllZCwgbWF0Y2hlZCwgdm90ZXJmaWxlKV0KCiMgY2hlY2sgcHJvcCBzdXJ2ZXllZCBhbmQgbWF0Y2hlZCBieSBkZW1vcwpkYXRhX3JlY29kZWRbLCAuKC5OLCBwcm9wX3N1cnZleWVkID0gbWVhbihzdXJ2ZXllZCksIHByb3BfbWF0Y2hlZCA9IHN1bShtYXRjaGVkKS9zdW0oc3VydmV5ZWQpLCBvdmVyYWxsX21hdGNoZWQgPSBtZWFuKG1hdGNoZWQpKSwgZGVtb19hZ2VfYnVja2V0XVtvcmRlcihkZW1vX2FnZV9idWNrZXQpXQpkYXRhX3JlY29kZWRbLCAuKC5OLCBwcm9wX3N1cnZleWVkID0gbWVhbihzdXJ2ZXllZCksIHByb3BfbWF0Y2hlZCA9IHN1bShtYXRjaGVkKS9zdW0oc3VydmV5ZWQpLCBvdmVyYWxsX21hdGNoZWQgPSBtZWFuKG1hdGNoZWQpKSwgZGVtb19wYXJ0eV1bb3JkZXIoZGVtb19wYXJ0eSldCgpgYGAKCiMjIERvIGJhc2ljIGRpc3QgcmVncmVzc2lvbiwgb25lIHN0ZXAgYXQgYSB0aW1lCgpGaXJzdCwgd2UnbGwgd2FsayB0aHJvdWdoIGVhY2ggc3RlcCBpbiBkaXN0cmlidXRpb24gcmVncmVzc2lvbiwgZml4aW5nIGh5cGVycGFyYW1ldGVyczoKCjEuICoqQmFnIHRoZSBkYXRhLioqIFNpbmNlIHdlIGFjdHVhbGx5IG9ic2VydmVkIG91ciBvdXRjb21lIGF0IHRoZSBpbmRpdmlkdWFsIGxldmVsIChub3QgYXQgdGhlIGJhZyBsZXZlbCksIHdlIGhhdmUgdG8gYmFnIHRoZSBkYXRhIG91cnNlbHZlcy4gV2UnbGwgZG8gdGhpcyB3aXRoIGstbWVhbnMgdXNpbmcgdGhlIHZhcmlhYmxlcyBvYnNlcnZlZCBib3RoIGluIHRoZSBzdXJ2ZXkgQU5EIGluIHRoZSB2b3RlcmZpbGUuICBUaGlzIGlzIG5lY2Vzc2FyeSBzbyB0aGF0IHdlIGNhbiBsaW5rIHRoZSBiYWdnZWQgb3V0Y29tZXMgdG8gdGhlIHZvdGVyZmlsZSBkYXRhLgoyLiAqKkdldCBsYW5kbWFyayBwb2ludHMuKiogSW4gb3JkZXIgdG8gY3V0IGRvd24gb24gdGhlIGRpbWVuc2lvbmFsaXR5IG9mIHRoZSB0YXNrLCB3ZSdsbCBpZGVudGlmeSBhIHNldCBvZiAkTCQgbGFuZG1hcmsgcG9pbnRzICRcbWF0aGJme3V9ID0gKHVfMSwgXGRvdHMsIHVfTCkkLCBhbHNvIHVzaW5nIGstbWVhbnMuICBUaGUgbGFuZG1hcmtzIHdpbGwgYmUgdGhlIGNlbnRyb2lkcyBvZiB0aGUgY2x1c3RlcnMgaW5kZW50aWZpZWQgd2l0aCBrLW1lYW5zLiAgV2UnbGwgdXNlIHRoZSBsYW5kbWFyayBwb2ludHMgdG8gZW1iZWQgdGhlIHZvdGVyZmlsZSBkYXRhIGluIGZlYXR1cmUgc3BhY2UuCjMuICoqRW1iZWQgaW4gZmVhdHVyZSBzcGFjZS4qKiAgV2UgbmVlZCB0byBlbWJlZCB0aGUgdm90ZXJmaWxlIGRhdGEgaW4gZmVhdHVyZSBzcGFjZS4gIFdlIGRvIHRoaXMgd2l0aCBhIEdhdXNzaWFuIGtlcm5lbCB3aXRoIGxlbmd0aC1zY2FsZSAkXHNpZ21hJC4gIFRoZSBleHBsaWNpdCBmZWF0dXJlIHJlcHJlc2VudGF0aW9uIGZvciAkeF9pJCBpcyBnaXZlbiBieSAkXHBoaSh4X2kpID0gW2soeF9pLCB1XzEpLCBcZG90cywgayh4X2ksIHVfTCldJAo0LiAqKkNhbGN1bGF0ZSB0aGUgZW1waXJpY2FsIG1lYW4gb2YgdGhlIGVtYmVkZGVkIGZlYXR1cmVzLioqIENhbGN1bGF0ZSB0aGUgZW1waXJpY2FsIG1lYW4gb2YgdGhlIGV4cGxpY2l0IGZlYXR1cmUgcmVwcmVzZW50YXRpb25zIG9mIGVhY2ggYmFnICRqID0gMSwgXGRvdHMsIEokLCAkXGhhdHtcbXV9X2ogPSBcc3VtX3tpPTF9XntOX2p9IFxwaGkoeF9pKSQKNS4gKipEbyByZWd1bGFyaXplZCByZWdyZXNzaW9uKiouICBSZWdyZXNzIHRoZSBiYWdnZWQgb3V0Y29tZSAkeV9qJCBvbiB0aGUgZW1waXJpY2FsIG1lYW4gZW1iZWRkaW5nIG9mIGVhY2ggYmFnICRcbXVfaiQuICBXZSB1c2UgYSBMQVNTTyBoZXJlLCB0aG91Z2ggd2UgY291bGQgdXNlIGVsYXN0aWMgbmV0IGFuZCBvcHRpbWl6ZSAkXGFscGhhJCBhcyB3ZWxsIGFzICRcbGFtYmRhJC4KNi4gKipQcmVkaWN0KiouICBVc2UgcHJlZGljdCAkXGhhdHt5fV9pJCAoYXQgdGhlIGluZGl2aWR1YWwtbGV2ZWwpIHVzaW5nIHRoZSByZWdyZXNzaW9uIGNvZWZmaWNpZW50cyBsZWFybmVkIGluIHN0ZXAgNS4KClRoZXNlIHN0ZXBzIGFyZSBpbXBsZW1lbnRlZCBiZWxvdy4KCiMjIyBTb21lIHF1ZXN0aW9uczoKCiogU2hvdWxkIHdlIHR1bmUgbl9iYWdzIGFuZCBuX2xhbmRtYXJrcyBsaWtlIHdlIGRvICRcc2lnbWEkPwoqIFNob3VsZCB3ZSBnZW5lcmF0ZSBiYWdzIGJhc2VkIG9uIHRoZSB3aG9sZSBkYXRhc2V0IChzdXJ2ZXkgKyB2b3RlcmZpbGUpLCBvciBqdXN0IGJhc2VkIG9uIHRoZSBzdXJ2ZXkgZGF0YT8gIFJpZ2h0IG5vdyBJJ20gZ2VuZXJhdGluZyB0aGUgYmFncyB1c2luZyBvbmx5IHRoZSBzdXJ2ZXkgZGF0YS4gSW4gcHJhY3RpY2UsIHRoZSB2b3RlcmZpbGUgZGF0YSB3aWxsIGhhdmUgTUFOWSBtb3JlIG9ic2VydmF0aW9ucyB0aGFuIHRoZSBzdXJ2ZXkgZGF0YSwgc28gaXQncyB2ZXJ5IHVubGlrZWx5IHRoYXQgdGhlIHN1cnZleSB3aWxsIGhhdmUgYmFncyB0aGF0IGFyZSBlbXB0eSBpbiB0aGhlIGZpbGUsIHdoaWxlIGlmIHdlIHVzZWQgb25seSB0aGUgdm90ZXJmaWxlIGRhdGEgb3Igdm90ZXJmaWxlICsgc3VydmV5LCBtdWNoIG1vcmUgbGlrZWx5IHRoYXQgYmFncyB3aWxsIGJlIG1pc3NpbmcgZnJvbSB0aGUgc3VydmV5IGRhdGEuCiogU2hvdWxkIHRoZSBsYW5kbWFya3MgYmUgYSBob2xkb3V0IHNldCBvciBjZW50cm9pZHMgZnJvbSBrLW1lYW5zPyAgQW5kdmFudGFnZXMvZGlzYWR2YW50YWdlcz8gIEkgaW1hZ2luZSBhbiBhZHZhbnRhZ2Ugb2YgdGhlIGNlbnRyb2lkcyBpcyB0aGF0IHlvdSBkb24ndCBsb3NlIE4gZm9yIHRoZSBtZWFuIGVtYmVkZGluZz8KKiBTaG91bGQgSSBoYXZlIGEgNXRoIGRhdGEgcGFydGl0aW9uIGZvciB0dW5pbmcgdGhlIGh5cGVycGFyYW1zIChyaWdodCBub3cgSSdtIHVzaW5nIHRoZSB0ZXN0IHNldCk/CiogU2hvdWxkIEkgc3dpdGNoIGZyb20gTEFTU08gdG8gY3YtY2hvc2VuICRcYWxwaGEkPwoKIyMjIyBBc3NpZ24gYmFncwpgYGB7cn0KIyMjIyBDcmVhdGUgYmFncyBvZiBzdXJ2ZXkgcmVzcG9uc2VzIHVzaW5nIHZhcmlhYmxlcyBvYnNlcnZlZCBpbiBCT1RIIHN1cnZleSBBTkQgdm90ZXJmaWxlCiMgc2hvdWxkIHdlIG1ha2UgdGhlIGJhZ3Mgd2l0aCB0aGUgc3Vic2V0IG9mIGRhdGEgZnJvbSB0aGUgc3VydmV5LCBvciB0aGUgd2hvbGUgZmlsZT8/PwoKIyBzaWdtYSA9IDAuMDAzICMjIGNob3NlbiB3aXRoIG1lZGlhbiBoZXVyaXN0aWMKbl9iYWdzID0gNTAKbl9sYW5kbWFya3MgPSAxMgoKYmFncyA9IGdldEJhZ3MoZGF0YSA9IGRhdGFfcmVjb2RlZFtzdXJ2ZXllZCA9PSAxLF0KICAgICAgICAsIHZhcnMgPSBmaWxlX2FuZF9zdXJ2ZXlfdmFycwogICAgICAgICwgbl9iYWdzID0gbl9iYWdzCiAgICAgICAgLCBuZXdkYXRhID0gZGF0YV9yZWNvZGVkW3ZvdGVyZmlsZSA9PSAxIHwgaG9sZG91dCA9PSAxLCBdKQp0YWJsZShiYWdzJGJhZ3MpCmxlbmd0aCh1bmlxdWUoYmFncyRiYWdzKSkKCmRhdGFfcmVjb2RlZFtzdXJ2ZXllZCA9PSAxLCBiYWcgOj0gYmFncyRiYWdzXQpkYXRhX3JlY29kZWRbdm90ZXJmaWxlID09IDEgfCBob2xkb3V0ID09IDEsIGJhZyA6PSBiYWdzJGJhZ3NfbmV3ZGF0YV0KCmRhdGFfcmVjb2RlZFssIC4oLk4sIG1lYW4oc3VydmV5ZWQpKSwgLihiYWcpXVtvcmRlcihiYWcpXQpgYGAKCiMjIyMgR2V0IGxhbmRtYXJrIHBvaW50cwpgYGB7cn0KbGFuZG1hcmtzID0gZ2V0TGFuZG1hcmtzKGRhdGEgPSBkYXRhX3JlY29kZWQKICAgICAgICAgICAgICwgdmFycyA9IHVuaXF1ZShjb3ZhcnNbaW5fYm90aCA9PSAxIHwgaW5fZmlsZSA9PSAxLF0kdmFyKQogICAgICAgICAgICAgLCBuX2xhbmRtYXJrcyA9IG5fbGFuZG1hcmtzCiAgICAgICAgICAgICAsIHN1YnNldF9pbmQgPSAoZGF0YV9yZWNvZGVkJHZvdGVyZmlsZSA9PSAxKSkKClhfZmlsZSA9IGxhbmRtYXJrcyRYW2RhdGFfcmVjb2RlZCR2b3RlcmZpbGUgPT0gMSwgXQoKYGBgCgojIyMjIEVtYmVkIGZpbGUgaW4gZmVhdHVyZSBzcGFjZQoKQ2hvb3NlIHNjYWxlIHBhcmFtZXRlciAkXHNpZ21hJCBmb3IgUkJGIGtlcm5lbCAtIHVzZSBtZWRpYW4gaGV1cmlzdGljIGZvciBub3cKYGBge3J9CnJiZjEgPSByYmZkb3Qoc2lnbWEgPSAxKQpLX3NpZ21hID0ga2VybmVsTWF0cml4KHJiZjEsIHggPSBhcy5tYXRyaXgoWF9maWxlKSwgeSA9IGxhbmRtYXJrcyRsYW5kbWFya3MpCnNpZ21hID0gbWVkaWFuKEtfc2lnbWEpCgp0cmFpbl9iYWcgPSBkYXRhX3JlY29kZWRbdm90ZXJmaWxlID09IDEsIGJhZ10KdGVzdF9iYWcgPSBkYXRhX3JlY29kZWRbaG9sZG91dCA9PSAxLCBiYWddCgojIGdldCBmZWF0dXJlcwpmZWF0dXJlcyA9IGdldEZlYXR1cmVzKGRhdGEgPSBsYW5kbWFya3MkWAogICAgICAgICAgICAgICAgICAgICAgICwgYmFnID0gZGF0YV9yZWNvZGVkJGJhZwogICAgICAgICAgICAgICAgICAgICAgICwgdHJhaW5faW5kID0gZGF0YV9yZWNvZGVkJHZvdGVyZmlsZSAjIHRoZSBjb2wgb2YgaW5kaWNhdG9ycyBmb3IgdGhlIHRyYWluaW5nIHNldAogICAgICAgICAgICAgICAgICAgICAgICwgbGFuZG1hcmtzID0gbGFuZG1hcmtzJGxhbmRtYXJrcwogICAgICAgICAgICAgICAgICAgICAgICwgc2lnbWEgPSBzaWdtYSkKYGBgCgojIyMjIEZpdCBMQVNTTyB1c2luZyBtZWFuIGZlYXR1cmUgZW1iZWRkaW5ncwpgYGB7cn0KIyBjYWxjdWxhdGUgZGVwZW5kZW50IHZhciBpbiBlYWNoIGJhZwpZX3N2eV9iYWcgPSBkYXRhX3JlY29kZWRbc3VydmV5ZWQgPT0gMSwgLih5X21lYW4gPSBtZWFuKHlfZGVtKSksIGJhZ11bb3JkZXIoYmFnKV0KIyBtYWtlIHN1cmUgWSBoYXMgYWxsIGxldmVscwpZX3N2eV9iYWcgPSBtZXJnZShkYXRhLnRhYmxlKGJhZyA9IDE6bl9iYWdzKSwgWV9zdnlfYmFnLCBhbGwueCA9IFQpCllfc3Z5X2JhZ1tpcy5uYSh5X21lYW4pLCB5X21lYW4gOj0gMF0KCgoKIyBkbyBiYXNpYyBEUgpmaXRfYmFzaWNEUiA9IGZpdExhc3NvKG11X2hhdCA9IGZlYXR1cmVzJG11X2hhdAogICAgICAgICAgLCBZX2JhZyA9IFlfc3Z5X2JhZyR5X21lYW4KICAgICAgICAgICwgcGhpX3ggPSBmZWF0dXJlcyRwaGlfeAogICAgICAgICAgKQoKIyBzY29yZSB0aGUgZmlsZQpkYXRhX3JlY29kZWRbLCB5X2RlbV9iYXNpY2RyIDo9IGZpdF9iYXNpY0RSJFldCgpjYWxjTVNFKFkgPSBkYXRhX3JlY29kZWQkeV9kZW0sIFlfcHJlZCA9IGRhdGFfcmVjb2RlZCR5X2RlbV9iYXNpY2RyKQpgYGAKCiMjIyBQbG90IHJlc3VsdHMKYGBge3J9CgpkYXRhX3JlY29kZWRbdm90ZXJmaWxlID09IDEgfCBob2xkb3V0ID09IDEsIHlfZGVtX2Jhc2ljZHJfZGVjIDo9IGN1dCh5X2RlbV9iYXNpY2RyLCBicmVha3MgPSBxdWFudGlsZSh5X2RlbV9iYXNpY2RyLCBwcm9icyA9IHNlcSgwLDEsMC4xKSksIGxhYmVscyA9IDE6MTAsIGluY2x1ZGUubG93ZXN0ID0gVCldCgpnZ3Bsb3QoZGF0YV9yZWNvZGVkW3ZvdGVyZmlsZSA9PSAxLCAuKHBjdF95X2RlbSA9IG1lYW4oeV9kZW0pKSwgYnkgPSB5X2RlbV9iYXNpY2RyX2RlY10sIGFlcyh4ID0geV9kZW1fYmFzaWNkcl9kZWMsIHkgPSBwY3RfeV9kZW0pKSArIAogIGdlb21fYmFyKHN0YXQgPSAnaWRlbnRpdHknKSArCiAgZ2d0aXRsZSgiUGN0IERlbSBzdXBwb3J0ZXIgYnkgc2NvcmUgZGVjaWxlIC0gdm90ZXJmaWxlIikKZ2dwbG90KGRhdGFfcmVjb2RlZFtob2xkb3V0ID09IDEsIC4ocGN0X3lfZGVtID0gbWVhbih5X2RlbSkpLCBieSA9IHlfZGVtX2Jhc2ljZHJfZGVjXSwgYWVzKHggPSB5X2RlbV9iYXNpY2RyX2RlYywgeSA9IHBjdF95X2RlbSkpICsgCiAgZ2VvbV9iYXIoc3RhdCA9ICdpZGVudGl0eScpICsKICBnZ3RpdGxlKCJQY3QgRGVtIHN1cHBvcnRlciBieSBzY29yZSBkZWNpbGUgLSBob2xkb3V0IikKYGBgCgojIyBTaW1wbGUgZGlzdHJpYnV0aW9uIHJlZ3Jlc3Npb24gd2l0aCB0dW5lZCBoeXBlcnBhcmFtZXRlcnMKCiMjIyBUdW5lIGh5cGVycGFyYW1ldGVycwpVc2UgaHlwZXJiYW5kIGFsZ29yaXRobSBkZXNjcmliZWQgaGVyZSAtIGh0dHBzOi8vbWVkaXVtLmNvbS9jcml0ZW8tbGFicy9oeXBlci1wYXJhbWV0ZXItb3B0aW1pemF0aW9uLWFsZ29yaXRobXMtMmZlNDQ3NTI1OTAzCgpNYWluIGFkdmFudGFnZSBvZiB0aGlzIG1ldGhvZCBhcmUgdGhhdCBpdCBydW5zIG11bHRpcGxlIGl0ZXJhdGlvbnMgd2l0aCB0aGUgc2FtZSBwYXJhbWV0ZXJzICh0aGVyZSdzIHN0aWxsIGFuIGVsZW1lbnQgb2YgcmFuZG9tbmVzcyBpbiBob3cgdGhlIGluaXRpdGFsIGNlbnRyb2lkcyBhcmUgY2hvc2VuIGluIGVhY2ggay1tZWFucyBydW4sIHNvIHRoaXMgaGVscHMgd2l0aCBldmFsdWF0aW5nIG1lYW4gcGVyZm9ybWFuY2Ugb2YgZWFjaCBzZXQgb2YgaHlwZXJwYXJhbXMpLgoKRGlzYWR2YW50YWdlIGlzIHRoYXQgd2Ugb25seSBjb25zaWRlciA2NCByYW5kb21seS1zZWxlY3RlZCBzZXRzIG9mIGh5cGVycGFyYW1ldGVycywgd2hpY2ggbWF5IG5vdCBmdWxseSBleHBsb3JlIHRoZSBzcGFjZSBlbm91Z2guICBUaG91Z2ggdGhlIGh5cGVycGFyYW1ldGVycyBjaG9zZW4gZG8gc2VlbSB0byB2YXN0bHkgaW1wcm92ZSBwZXJmb3JtYW5jZSBvdmVyIGluaXRpYWwgKG5vbi10dW5lZCkgY2hvaWNlcy4KCgpgYGB7ciBocC1vcHQsIGNhY2hlID0gVH0KbGlicmFyeShwYXJhbGxlbCkKbGlicmFyeShNQVNTKQoKIyB0YWtlcyBhIHdoaWxlLCBzbyBkb24ndCBydW4gYnkgYWNjaWRlbnQvYXV0b21hdGljYWxseQppZihGQUxTRSl7CiAgIyBodHRwczovL21lZGl1bS5jb20vY3JpdGVvLWxhYnMvaHlwZXItcGFyYW1ldGVyLW9wdGltaXphdGlvbi1hbGdvcml0aG1zLTJmZTQ0NzUyNTkwMwogIG5fcGFyYW1fc2V0cyA9IDY0CiAgcmVzdWx0cyA9IGRhdGEudGFibGUoc2lnbWEgPSBleHAocnVuaWYobWluID0gLTE1LCBtYXggPSAtMSwgbiA9IG5fcGFyYW1fc2V0cykpCiAgICAgICAgICAgICAgICAgICAgICAgLCBuX2xhbmRtYXJrcyA9IHJvdW5kKHJ1bmlmKG1pbiA9IDEwLCBtYXggPSA0MDAsIG4gPSBuX3BhcmFtX3NldHMpKQogICAgICAgICAgICAgICAgICAgICAgICwgbl9iYWdzID0gcm91bmQoZXhwKHJ1bmlmKG1pbiA9IDIsIG1heCA9IDUsIG4gPSBuX3BhcmFtX3NldHMpKSkKICAgICAgICAgICAgICAgICAgICAgICAsIHJvdW5kID0gcmVwKDAsIG5fcGFyYW1fc2V0cykKICAgICAgICAgICAgICAgICAgICAgICAsIG1zZSA9IHJlcCgwLCBuX3BhcmFtX3NldHMpCiAgICAgICAgICAgICAgICAgICAgICAgLCBtc2VfY291bnQgPSByZXAoMCwgbl9wYXJhbV9zZXRzKQogICkKICAjc2V0bmFtZXMocmVzdWx0cywgYygnc2lnbWEnLCduX2xhbmRtYXJrcycsICduX2JhZ3MnLCAnbXNlJykpCiAgCiAgI2luaXRpYWxpemUgY291bnRlcnMKICB3aGljaF9sZWZ0ID0gMTpuX3BhcmFtX3NldHMKICBuX2l0ZXIgPSAxMAogIAogIHdoaWxlKGxlbmd0aCh3aGljaF9sZWZ0KSA+IDEpewogICAgY2F0KHBhc3RlKFN5cy50aW1lKCksICJyb3VuZDogIiwgbWF4KHJlc3VsdHNbLCByb3VuZF0gKyAxKSksICcsIHJlbWFpbmluZzogJyxsZW5ndGgod2hpY2hfbGVmdCksJ1xuJykKICAgIAogICAgIyBkb3VibGUgdGhlIG51bWJlciBvZiBpdGVyYXRpb25zCiAgICAjaWYodGhpc19yb3VuZCA+IDMpIG5faXRlciA9IDUwCiAgICAKICAgIGZvcihwIGluIHdoaWNoX2xlZnQpewogICAgICAjY2F0KHBhc3RlKCdcdCBwYXJhbTonLCBwLCAnXG4nKSkKICAgICAgCiAgICAgICMgaW5jcmVtZW50IHJvdW5kCiAgICAgIHJlc3VsdHNbcCwgcm91bmQgOj0gcm91bmQgKyAxXQogICAgICAKICAgICAgbXNlX3N1bSA9IHVubGlzdChtY2xhcHBseSgxOm5faXRlciwgZnVuY3Rpb24oaSl7CiAgICAgICAgdHJ5Q2F0Y2goZG9CYXNpY0RSKGRhdGEgPSBkYXRhX3JlY29kZWQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAsIGJhZ2dpbmdfdmFycyA9IGZpbGVfYW5kX3N1cnZleV92YXJzCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLCByZWdyZXNzaW9uX3ZhcnMgPSB1bmlxdWUoY292YXJzW2luX2JvdGggPT0gMSB8IGluX2ZpbGUgPT0gMSxdJHZhcikKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjLCBvdXRjb21lID0gJ3lfZGVtJwogICAgICAgICAgICAgICAgICAgICAgICAgICAsIG91dGNvbWUgPSBjKCd5X2RlbScsICd5X3JlcCcsICd5X290aCcpCiAgICAgICAgICAgICAgICAgICAgICAgICAgICwgZmFtaWx5ID0gJ211bHRpbm9taWFsJwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICwgbl9iYWdzID0gcmVzdWx0c1twLCBuX2JhZ3NdCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLCBuX2xhbmRtYXJrcyA9IHJlc3VsdHNbcCwgbl9sYW5kbWFya3NdCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLCBzaWdtYSA9IHJlc3VsdHNbcCwgc2lnbWFdCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLCBiYWdnaW5nX2luZCA9ICdzdXJ2ZXllZCcKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAsIHRyYWluX2luZCA9ICd2b3RlcmZpbGUnCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLCB0ZXN0X2luZCA9ICdob2xkb3V0JwogICAgICAgICkkbXNlX3Rlc3QKICAgICAgICAsIGVycm9yID0gZnVuY3Rpb24oZSkgewogICAgICAgICAgcHJpbnQoZSkKICAgICAgICAgIHJldHVybigwKX0pCiAgICAgICAgCiAgICAgICAgCiAgICAgIH0sIG1jLmNvcmVzID0gNSAgI25lZWQgdGhpcyBmb3IgcGFyYWxsZWxpemF0aW9uCiAgICAgICkpCiAgICAgIHJlc3VsdHNbcCwgbXNlIDo9IG1zZSArIHN1bShtc2Vfc3VtKV0KICAgICAgcmVzdWx0c1twLCBtc2VfY291bnQgOj0gbXNlX2NvdW50ICsgc3VtKG1zZV9zdW0gPiAwKV0KICAgIH0KICAgIHdoaWNoX2xlZnQgPSB3aGljaChyZXN1bHRzW3doaWNoX2xlZnQsIG1zZS9tc2VfY291bnQgPCBtZWRpYW4obXNlL21zZV9jb3VudCwgbmEucm0gPSBUKV0pCiAgICAKICB9CgoKfQoKIyBiZXN0CnJlc3VsdHNbcm91bmQgPT0gbWF4KHJvdW5kKV0KYGBgCgoKCiMjIEZpdCBzaW1wbGUgZGlzdHJpYnV0aW9uIHJlZ3Jlc3Npb24gd2l0aCBvcHRpbWFsIGh5cGVycGFyYW1zCmBgYHtyIGZpdC1kcn0KYmFnZ2luZ192YXJzID0gZmlsZV9hbmRfc3VydmV5X3ZhcnMKcmVncmVzc2lvbl92YXJzID0gdW5pcXVlKGNvdmFyc1tpbl9ib3RoID09IDEgfCBpbl9maWxlID09IDEsXSR2YXIpCgojIHBhcmFtcyBmcm9tIHBlcl9oeXBlcnBhcmFtX29wdC5SCmZpdF9iYXNpY0RSID0gZG9CYXNpY0RSKGRhdGEgPSBkYXRhX3JlY29kZWQKICAgICAgICAgICAgICAgICAgICAgLCBiYWdnaW5nX3ZhcnMgPSBmaWxlX2FuZF9zdXJ2ZXlfdmFycwogICAgICAgICAgICAgICAgICAgICAsIHJlZ3Jlc3Npb25fdmFycyA9IHJlZ3Jlc3Npb25fdmFycwogICAgICAgICAgICAgICAgICAgICAsIG91dGNvbWUgPSAneV9kZW0nCiAgICAgICAgICAgICAgICAgICAgICwgbl9iYWdzID0gOTUKICAgICAgICAgICAgICAgICAgICAgLCBuX2xhbmRtYXJrcyA9IDQ1CiAgICAgICAgICAgICAgICAgICAgICwgc2lnbWEgPSAwLjAwNAogICAgICAgICAgICAgICAgICAgICAsIGJhZ2dpbmdfaW5kID0gJ3N1cnZleWVkJwogICAgICAgICAgICAgICAgICAgICAsIHRyYWluX2luZCA9ICd2b3RlcmZpbGUnCiAgICAgICAgICAgICAgICAgICAgICwgdGVzdF9pbmQgPSAnaG9sZG91dCcpCmBgYAoKIyMgUGxvdCByZXN1bHRzCmBgYHtyfQoKIyBnZXQgc2NvcmUgZGVjaWxlcwpmaXRfYmFzaWNEUiRkYXRhW3ZvdGVyZmlsZSA9PSAxIHwgaG9sZG91dCA9PSAxLCB5X2hhdF9kZWMgOj0gY3V0KHlfZGVtX2hhdCwgYnJlYWtzID0gcXVhbnRpbGUoeV9kZW1faGF0LCBwcm9icyA9IHNlcSgwLDEsMC4xKSksIGxhYmVscyA9IDE6MTAsIGluY2x1ZGUubG93ZXN0ID0gVCldCgojIHBsb3Qgdm90ZXJmaWxlIGRhdGEKZ2dwbG90KGZpdF9iYXNpY0RSJGRhdGFbdm90ZXJmaWxlID09IDEsIC4ocGN0X3lfaGF0ID0gbWVhbih5X2RlbV9oYXQpKSwgYnkgPSB5X2hhdF9kZWNdLCBhZXMoeCA9IHlfaGF0X2RlYywgeSA9IHBjdF95X2hhdCkpICsKICBnZW9tX2JhcihzdGF0ID0gJ2lkZW50aXR5JykgKwogIGdndGl0bGUoIlBjdCBEZW0gc3VwcG9ydGVyIGJ5IHNjb3JlIGRlY2lsZSAtIHZvdGVyZmlsZSIpCgojIHBsb3QgaG9sZG91dCBkYXRhCmdncGxvdChmaXRfYmFzaWNEUiRkYXRhW2hvbGRvdXQgPT0gMSwgLihwY3RfeV9oYXQgPSBtZWFuKHlfZGVtX2hhdCkpLCBieSA9IHlfaGF0X2RlY10sIGFlcyh4ID0geV9oYXRfZGVjLCB5ID0gcGN0X3lfaGF0KSkgKyAKICBnZW9tX2JhcihzdGF0ID0gJ2lkZW50aXR5JykgKwogIGdndGl0bGUoIlBjdCBEZW0gc3VwcG9ydGVyIGJ5IHNjb3JlIGRlY2lsZSAtIGhvbGRvdXQiKQpgYGAKCgojIyBDb21wYXJlIHRvIGJhc2ljIExBU1NPIGFuZCBncm91cCBhdmVyYWdlCgpBcyBhIGJhc2VsaW5lLCB3ZSdsbCBmaXQgYSBzaW1wbGUgTEFTU08gdXNpbmcgb25seSB0aGUgJG5fbWF0Y2hlZCQgb2JzZXJ2YXRpb25zIHRoYXQgd2VyZSBtYXRjaGVkIHRvIHRoZSBmaWxlLiAgV2UgZml0IHRoZSBMQVNTTyBvbmNlIHRvIGZpbmQgd2hpY2ggY29lZnMgYXJlIG5vbi16ZXJvLCBhbmQgdGhlbiByZS1maXQgd2l0aCBvbmx5IHRob3NlIGNvdmFycyBhbmQgbm8gcGVuYWx0eSB0byBhdm9pZCBzaHJpbmthZ2UuCgpUaGUgb3RoZXIgYmFzZWxpbmUgd2UnbGwgdXNlIGlzIHRoZSBtZWFuIGluIGVhY2ggYmFnIG9ic2VydmVkIGluIHRoZSBzdXJ2ZXkgZGF0YSAobWF0Y2hlZCBhbmQgdW5tYXRjaGVkKS4gIFRoaXMgaXMgb3VyIGRlcGVuZGVudCB2YXJpYWJsZSBmb3IgdGhlIGRpc3RyaWJ1dGlvbiByZWdyZXNzaW9uLgoKRml0LCBwcmVkaWN0LCBhbmQgcGxvdCByZXN1bHRzCmBgYHtyIGJhc2ljLWxhc3NvfQoKWF9tYXRjaGVkID0gbGFuZG1hcmtzJFhbZGF0YV9yZWNvZGVkJG1hdGNoZWQgPT0gMSwgXQpsYXNzb19maXQgPSBjdi5nbG1uZXQoeCA9IFhfbWF0Y2hlZAogICAgICAgICAgICAgICAgICAgICAgLCB5ID0gZGF0YV9yZWNvZGVkW21hdGNoZWQgPT0gMSwgXSR5X2RlbQogICAgICAgICAgICAgICAgICAgICAgLCBuZm9sZHMgPSAxMAogICAgICAgICAgICAgICAgICAgICAgLCBmYW1pbHkgPSAnYmlub21pYWwnKQpub256ZXJvX2luZCA9IHdoaWNoKGNvZWYobGFzc29fZml0LCBzID0gJ2xhbWJkYS5taW4nKVstMV0gIT0gMCkKCiMgcmUtZml0IHRvIGF2b2lkIHNocmlua2FnZQpsYXNzb19maXQgPSBnbG1uZXQoeCA9IFhfbWF0Y2hlZFssIG5vbnplcm9faW5kXQogICAgICAgICAgICAgICAgICAgLCB5ID0gZGF0YV9yZWNvZGVkW21hdGNoZWQgPT0gMSwgXSR5X2RlbQogICAgICAgICAgICAgICAgICAgLCBsYW1iZGEgPSAwCiAgICAgICAgICAgICAgICAgICAsIGZhbWlseSA9ICdiaW5vbWlhbCcpCgojIHByZWRpY3Qgb24gZnVsbCBkYXRhc2V0CmRhdGFfcmVjb2RlZFssIHlfaGF0X2xhc3NvIDo9IHByZWRpY3QobGFzc29fZml0LCBuZXd4ID0gbGFuZG1hcmtzJFhbLCBub256ZXJvX2luZF0sIHR5cGUgPSAncmVzcG9uc2UnKV0KCiMgZ2V0IHNjb3JlIGRlY2lsZXMKZGF0YV9yZWNvZGVkW2hvbGRvdXQgPT0gMSwgeV9oYXRfbGFzc29fZGVjIDo9IGN1dCh5X2hhdF9sYXNzbywgYnJlYWtzID0gcXVhbnRpbGUoeV9oYXRfbGFzc28sIHByb2JzID0gc2VxKDAsMSwwLjEpKSwgbGFiZWxzID0gMToxMCwgaW5jbHVkZS5sb3dlc3QgPSBUKV0KCiMgcGxvdCBhdmcgb3V0Y29tZSBieSBzY29yZSBkZWNpbGUKZ2dwbG90KGRhdGFfcmVjb2RlZFtob2xkb3V0ID09IDEsIC4ocGN0X3lfaGF0ID0gbWVhbih5X2hhdF9sYXNzbykpLCBieSA9IHlfaGF0X2xhc3NvX2RlY10sIGFlcyh4ID0geV9oYXRfbGFzc29fZGVjLCB5ID0gcGN0X3lfaGF0KSkgKyAKICBnZW9tX2JhcihzdGF0ID0gJ2lkZW50aXR5JykgKwogIGdndGl0bGUoIlBjdCBEZW0gc3VwcG9ydGVyIGJ5IHNjb3JlIGRlY2lsZSAtIGhvbGRvdXQiKQpgYGAKTWVyZ2UgYmFnIGF2ZXJhZ2Ugd2l0aCBmdWxsIGZpbGUKYGBge3J9CmRhdGFfcmVjb2RlZFtZX3N2eV9iYWcsIG9uID0gJ2JhZycsIHlfYmFnbWVhbiA6PSBpLnlfbWVhbl0KYGBgCgoKQ2FsY3VsYXRlIHRoZSBNU0Ugb2YgZWFjaCB0aGUgYmFzaWMgRFIgYW5kIHRoZSBMQVNTTwpgYGB7cn0KZGF0YS5mcmFtZShtZXRob2QgPSBjKCJEaXN0IFJlZyIsICJMb2dpdCIsICJCYWcgbWVhbiIpCiwgTVNFID0gYyhjYWxjTVNFKFkgPSBkYXRhX3JlY29kZWRbaG9sZG91dCA9PSAxLCB5X2RlbV0sIGRhdGFfcmVjb2RlZFtob2xkb3V0ID09IDEsIHlfZGVtX2hhdF0pCiAgICAgICAgLCBjYWxjTVNFKFkgPSBkYXRhX3JlY29kZWRbaG9sZG91dCA9PSAxLCB5X2RlbV0sIGRhdGFfcmVjb2RlZFtob2xkb3V0ID09IDEsIHlfaGF0X2xhc3NvXSkKICAgICAgICAsIGNhbGNNU0UoWSA9IGRhdGFfcmVjb2RlZFtob2xkb3V0ID09IDEsIHlfZGVtXSwgZGF0YV9yZWNvZGVkW2hvbGRvdXQgPT0gMSwgeV9iYWdtZWFuXSkpKQpgYGAKClNlZSBob3cgd2VsbCBlYWNoIG1ldGhvZCBpcyBwcmVkaWN0aW5nIHRoZSBvdmVyYWxsIHRvcGxpbmUgJSBkZW0gc3VwcG9ydCAoYmlhcykKYGBge3J9CmRhdGFfcmVjb2RlZFssIC4oYWN0dWFsID0gbWVhbih5X2RlbSkKICAgICAgICAgICAgICAgICAsIGJhc2ljZHIgPSBtZWFuKHlfZGVtX2hhdCkKICAgICAgICAgICAgICAgICAsIGxvZ2l0ID0gbWVhbih5X2hhdF9sYXNzbykKICAgICAgICAgICAgICAgICAsIGdyb3VwYXZnID0gbWVhbih5X2JhZ21lYW4sIG5hLnJtID1UKSkKICAgICAgICAgICAgICwgYnkgPSAuKHBhcnRpdGlvbiA9IGlmZWxzZShob2xkb3V0ID09IDEsICdob2xkb3V0JywgaWZlbHNlKHZvdGVyZmlsZSA9PSAxLCAndm90ZXJmaWxlJywgaWZlbHNlKG1hdGNoZWQgPT0gMSwgJ3N1cnZleS1tYXRjaGVkJywgJ3N1cnZleS11bm1hdGNoZWQnKSkpKV0KYGBgCgojIyBTb21lIG9ic2VydmF0aW9ucwoKKiBUaGUgY2hvaWNlIG9mIHdoZXJlIHZhcmlhYmxlcyBhcmUgb2JzZXJ2ZWQgKHN1cnZleSwgZmlsZSBvciBib3RoKSBoYXMgYSBsYXJnZSBpbXBhY3Qgb24gcmVzdWx0cy4gIEZvciBleGFtcGxlLCBpZiB3ZSBvYnNlcnZlIHNvbWVvbmUncyBzdGF0ZWQgcGFydHkgYWZmaWxpYXRpb24gYXQgdGhlIGluZGl2aWR1YWwtbGV2ZWwgaW4gdGhlIHZvdGVyZmlsZSwgdGhlIGxvZ2l0IHZhc3RseSBvdXRwZXJmb3JtcyB0aGUgZGlzdHJpYnV0aW9uIHJlZ3Jlc3Npb24gKGV4LiBNU0UgMC4wOCB2LiAwLjI1KS4gIEhvd2V2ZXIsIGlmIHdlIG9ubHkgb2JzZXJ2ZSB0aGF0IHZhcmlhYmxlIGluIHRoZSBzdXJ2ZXksIHRoZSBkaXN0cmlidXRpb24gcmVncmVzaW9uIGRvZXMgYWxtb3N0IGFzIHdlbGwgKGV4LiBNU0UgMC4yMiB2LiAwLjI1KS4KKiAKCgojIyBNdWx0aW5vbWlhbCBPdXRjb21lCgpOb3cgd2UnbGwgc3dpdGNoIHRvIGEgbXVsdGlub21pYWwgb3V0Y29tZSAkXG1hdGhiZnt5fSA9ICh5X1x0ZXh0e2RlbX0sIHlfXHRleHR7cmVwfSwgeV9cdGV4dHtvdGhlcn0pJAoKYGBge3IgZHItbXVsdGlub219Cm91dGNvbWUgPSBjKCd5X2RlbScsICd5X3JlcCcsICd5X290aCcpCgojIHBhcmFtcyBmcm9tIHBlcl9oeXBlcnBhcmFtX29wdC5SCmZpdF9iYXNpY0RSID0gZG9CYXNpY0RSKGRhdGEgPSBkYXRhX3JlY29kZWQKICAgICAgICAgICAgICAgICAgICAgLCBiYWdnaW5nX3ZhcnMgPSBmaWxlX2FuZF9zdXJ2ZXlfdmFycwogICAgICAgICAgICAgICAgICAgICAsIHJlZ3Jlc3Npb25fdmFycyA9IHVuaXF1ZShjb3ZhcnNbaW5fYm90aCA9PSAxIHwgaW5fZmlsZSA9PSAxLF0kdmFyKQogICAgICAgICAgICAgICAgICAgICAsIG91dGNvbWUgPSBvdXRjb21lCiAgICAgICAgICAgICAgICAgICAgICwgbl9iYWdzID0gMTMxCiAgICAgICAgICAgICAgICAgICAgICwgbl9sYW5kbWFya3MgPSAzMgogICAgICAgICAgICAgICAgICAgICAsIHNpZ21hID0gMC4wMDYKICAgICAgICAgICAgICAgICAgICAgLCBmYW1pbHkgPSAnbXVsdGlub21pYWwnCiAgICAgICAgICAgICAgICAgICAgICwgYmFnZ2luZ19pbmQgPSAnc3VydmV5ZWQnCiAgICAgICAgICAgICAgICAgICAgICwgdHJhaW5faW5kID0gJ3ZvdGVyZmlsZScKICAgICAgICAgICAgICAgICAgICAgLCB0ZXN0X2luZCA9ICdob2xkb3V0JykKCmZpdF9iYXNpY0RSJG1zZV90ZXN0CgpgYGAKCkNhbGN1bGF0ZSBncm91cCBtZWFucwpgYGB7cn0KWV9ncnBfbWVhbnMgPSBmaXRfYmFzaWNEUiRkYXRhW3N1cnZleWVkID09IDEsIGxhcHBseSguU0QsIG1lYW4pLCAuU0Rjb2xzID0gb3V0Y29tZSwgYnkgPSBiYWddCnNldG5hbWVzKFlfZ3JwX21lYW5zLCBjKCdiYWcnLHBhc3RlMChvdXRjb21lLCAnX2dycG1lYW4nKSkpCmRhdGFfcmVjb2RlZCA9IG1lcmdlKGRhdGFfcmVjb2RlZCwgWV9ncnBfbWVhbnMsIGJ5ID0gJ2JhZycsIGFsbC54ID0gVCkKCmBgYAoKRml0IG11bHRpbm9taWFsIExBU1NPCmBgYHtyfQpYX21hdGNoZWQgPSBsYW5kbWFya3MkWFtkYXRhX3JlY29kZWQkbWF0Y2hlZCA9PSAxLCBdCmxhc3NvX2ZpdCA9IGN2LmdsbW5ldCh4ID0gWF9tYXRjaGVkCiAgICAgICAgICAgICAgICAgICAgICAsIHkgPSBhcy5tYXRyaXgoZGF0YV9yZWNvZGVkW21hdGNoZWQgPT0gMSwgd2hpY2gobmFtZXMoZGF0YV9yZWNvZGVkKSAlaW4lIG91dGNvbWUpLCB3aXRoID0gRl0pCiAgICAgICAgICAgICAgICAgICAgICAsIG5mb2xkcyA9IDEwCiAgICAgICAgICAgICAgICAgICAgICAsIGZhbWlseSA9ICdtdWx0aW5vbWlhbCcKICAgICAgICAgICAgICAgICAgICAgICkKbm9uemVyb19pbmQgPSBzb3J0KHVuaXF1ZSh1bmxpc3QobGFwcGx5KGNvZWYobGFzc29fZml0LCBzID0gJ2xhbWJkYS5taW4nKSwgZnVuY3Rpb24oYyl7CiAgICAgIHdoaWNoKGNbLTFdICE9IDApCiAgICB9KSkpKQoKaWYobGVuZ3RoKG5vbnplcm9faW5kKSA9PSAwKXsKICBub256ZXJvX2luZCA9IDE6bmNvbChYX21hdGNoZWQpCn0KCiMgIyByZS1maXQgdG8gYXZvaWQgc2hyaW5rYWdlCmxhc3NvX2ZpdCA9IGdsbW5ldCh4ID0gWF9tYXRjaGVkWywgbm9uemVyb19pbmRdCiAgICAgICAgICAgICAgICAgICAsIHkgPSBhcy5tYXRyaXgoZGF0YV9yZWNvZGVkW21hdGNoZWQgPT0gMSwgd2hpY2gobmFtZXMoZGF0YV9yZWNvZGVkKSAlaW4lIG91dGNvbWUpLCB3aXRoID0gRl0pCiAgICAgICAgICAgICAgICAgICAsIGxhbWJkYSA9IDAKICAgICAgICAgICAgICAgICAgICwgZmFtaWx5ID0gJ211bHRpbm9taWFsJykKCiMgcHJlZGljdCBvbiBmdWxsIGRhdGFzZXQKZGF0YV9yZWNvZGVkWywgYygneV9sb2dpdF9kZW0nLCAneV9sb2dpdF9yZXAnLCAneV9sb2dpdF9vdGgnKSA6PSBhcy5saXN0KGRhdGEudGFibGUobWF0cml4KGxvZ2l0X211bHRpbm9tLCBuY29sPSAzKSkpXQpgYGAKCkNvbXBhcmUgdGhlIE1TRXMgb2YgdGhlIGhvbGRvdXQgc2V0CmBgYHtyfQojIGNvbXBhcmUgTVNFCmRhdGEuZnJhbWUoCiAgbWV0aG9kID0gYygnRGlzdCBSZWcnLCAnTG9naXQnLCdCYWcgYXZnJykKICAsIE1TRSA9IGMoY2FsY01TRShZID0gZGF0YV9yZWNvZGVkW2hvbGRvdXQgPT0gMSwgZ2V0KG91dGNvbWUpXSwgZGF0YV9yZWNvZGVkW2hvbGRvdXQgPT0gMSwgZ2V0KHBhc3RlMChvdXRjb21lLCAnX2hhdCcpKV0pCiAgICAgICAgICAgICwgY2FsY01TRShZID0gZGF0YV9yZWNvZGVkW2hvbGRvdXQgPT0gMSwgZ2V0KG91dGNvbWUpXSwgZGF0YV9yZWNvZGVkW2hvbGRvdXQgPT0gMSwgZ2V0KGMoJ3lfbG9naXRfZGVtJywgJ3lfbG9naXRfcmVwJywgJ3lfbG9naXRfb3RoJykpXSkKICAgICAgICAgICAgLCBjYWxjTVNFKFkgPSBkYXRhX3JlY29kZWRbaG9sZG91dCA9PSAxLCBnZXQob3V0Y29tZSldLCBkYXRhX3JlY29kZWRbaG9sZG91dCA9PSAxLCBnZXQoYygneV9kZW1fZ3JwbWVhbicsICd5X3JlcF9ncnBtZWFuJywgJ3lfb3RoX2dycG1lYW4nKSldKSkKICApCgpgYGAKCg==